Skip to content

fix(web): repair news ticker custom-feeds save for JSON path#376

Merged
ChuckBuilds merged 4 commits into
mainfrom
fix/news-ticker-json-array-validation
Jun 29, 2026
Merged

fix(web): repair news ticker custom-feeds save for JSON path#376
ChuckBuilds merged 4 commits into
mainfrom
fix/news-ticker-json-array-validation

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Primary fix: The backend JSON request path was never calling fix_array_structures, so saving news ticker custom feeds always failed schema validation. dotToNested() in the frontend converts indexed form fields (feeds.custom_feeds.0.name) into a dict with numeric string keys ({'0': {...}}), but the schema expects type array. Added _fix_json_arrays() immediately after JSON schema loading to mirror what fix_array_structures() already does for form-data submissions.
  • Secondary fix: custom-feeds.js getValue() returned logo: null when no logo was present. Since logo expects type object, this would fail schema validation if the function is ever called during a save. Changed to omit the key entirely when absent.

Root cause

save_plugin_config() in api_v3.py has two branches — form-data and JSON. The web UI always sends Content-Type: application/json, so only the JSON branch runs. fix_array_structures() was defined inside the else (form-data) branch and never reached for UI saves. Any config with at least one custom feed would fail with:

"Field 'feeds.custom_feeds': Expected type array, got object"

Test plan

  • Open News Ticker plugin config → add a custom feed (name + URL, no logo) → Save → success notification appears, no console errors
  • Add multiple custom feeds → Save → success
  • Remove a feed (re-indexes remaining rows) → Save → success
  • Upload a logo to a feed → Save → success
  • Reload page → confirm saved feeds persist correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved install reliability with better handling of package-manager locks, low disk space checks, and clearer failure logs.
    • Made dependency installation more resilient by capturing more useful error output and improving fallback behavior.
    • Fixed plugin configuration saving for nested arrays and numeric values, helping forms save correctly.
    • Improved custom feed saving so logo data is only included when present.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ChuckBuilds, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6234eaea-44e8-4767-8c5e-2f6a21072c8d

📥 Commits

Reviewing files that changed from the base of the PR and between 7e44ad3 and 04f96ec.

📒 Files selected for processing (2)
  • web_interface/blueprints/api_v3.py
  • web_interface/templates/v3/partials/tools.html
📝 Walkthrough

Walkthrough

The installer scripts gain APT lock waiting, disk-space preflight, rpi-rgb-led-matrix clone/build retries with captured output, typed return values and failure summaries in the dependency installer, and unbuffered Python invocation. The web interface adds recursive normalization of numeric-key dict arrays in plugin config saves and conditionally omits logo from custom-feed rows.

Changes

Installer robustness

Layer / File(s) Summary
APT lock, disk check, and error logging
first_time_install.sh
on_error prints 100 log lines (was 50); wait_for_apt_lock() polls /var/lib/dpkg/lock-frontend up to 180s; check_disk_space() hard-fails below 500MB and warns below 1GB; apt_update/apt_install use retry with -o DPkg::Lock::Timeout=180; preflight runs check_network then check_disk_space before apt_update.
rpi-rgb-led-matrix retry and pip build capture
first_time_install.sh
_clone_rpi_rgb() removes partial destination directories before cloning and is routed through retry for both submodule init and direct-clone fallback; pip build output is captured to a temp file with BUILD_SUCCESS tracking and tail-printed on failure; smart web dependency installer invoked with python3 -u.
Typed run helper and failure summary
scripts/install_dependencies_apt.py
_run() streams combined stdout/stderr to a temp file and returns (success, output_tail); install_via_apt/install_via_pip return typed (bool, str) tuples; check_package_installed uses IMPORT_NAME_MAP for module-name translation; print_failure_summary prints captured tail lines per failed package; rgbmatrix local install drops --ignore-installed.

Web UI normalization

Layer / File(s) Summary
Plugin config array normalization and feed logo handling
web_interface/blueprints/api_v3.py, web_interface/static/v3/js/widgets/custom-feeds.js
save_plugin_config recursively converts schema-defined array fields submitted as numeric-key dicts into sorted lists, coercing integer/number elements; custom-feeds.js getValue only attaches logo to a feed object when logo-related inputs are present in the row.
tools.html blank-line fix
web_interface/templates/v3/partials/tools.html
One blank line added after return; in the loadGitInfo() error branch; no functional change.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ChuckBuilds/LEDMatrix#225: Modifies the same smart/web dependency installation step in first_time_install.sh, overlapping with the unbuffered Python invocation and pip install logic.
  • ChuckBuilds/LEDMatrix#329: Modifies api_v3.py recursive config normalization helpers (_set_missing_booleans_to_false, _filter_config_by_schema) that share the same plugin config shaping code path updated here.
  • ChuckBuilds/LEDMatrix#369: Directly overlaps with first_time_install.sh APT lock waiting, disk preflight, rpi-rgb-led-matrix clone/retry logic, and install_dependencies_apt.py output capture changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing News Ticker custom-feeds saving for the JSON request path.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/news-ticker-json-array-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Jun 26, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
scripts/install_dependencies_apt.py (1)

20-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add type hints to the new installer helpers.

The new tuple-returning helpers are harder to follow without annotations.

As per coding guidelines, "Use type hints for function parameters and return values."

Also applies to: 40-68, 71-90, 100-127, 130-130

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install_dependencies_apt.py` around lines 20 - 37, The new installer
helper functions, including _run and the related helper routines in
install_dependencies_apt.py, are missing type annotations for parameters and
return values, which makes the tuple-returning behavior harder to understand.
Add explicit type hints to each helper’s signature, including _run’s command
argument and boolean/string tuple return, and apply the same treatment to the
other newly added helper methods/functions referenced in the diff so their
inputs and outputs are clear and consistent with the coding guidelines.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@first_time_install.sh`:
- Around line 867-874: The fallback clone path in first_time_install.sh is not
retry-safe because retry git clone can leave rpi-rgb-led-matrix-master behind
and cause later attempts to fail with an existing-destination error. Update both
clone paths around the submodule fallback so they either remove any partial
rpi-rgb-led-matrix-master before each clone attempt or clone into a temporary
directory and move it into place only after success.
- Around line 210-228: The wait_for_apt_lock() helper is using flock against
/var/lib/dpkg/lock-frontend, but that does not wait on apt/dpkg’s actual fcntl
lock, so it can proceed while dpkg is still busy. Update wait_for_apt_lock() to
use an apt/dpkg-aware wait mechanism such as DPkg::Lock::Timeout, keeping the
existing logging and timeout behavior intact, and ensure the logic still blocks
until the package lock is truly available.

In `@scripts/install_dependencies_apt.py`:
- Around line 62-68: The APT install path in install_dependencies_apt.py still
bypasses the new lock/retry behavior, so update the apt-based dependency install
flow to use the same wait-and-retry wrapper as first_time_install.sh instead of
calling sudo apt directly. Apply this consistently in the installation logic
around the apt_package install path and the other apt command sites noted in the
file so lock contention does not incorrectly trigger a pip fallback; keep the
existing success/failure handling in the install helper, but route APT execution
through the shared retry-aware path.

In `@web_interface/blueprints/api_v3.py`:
- Around line 1640-1646: The clear_pycache branch in api_v3.py currently always
returns success because shutil.rmtree is called with ignore_errors=True, which
can hide permission or filesystem failures. Update the clear_pycache action to
detect and handle deletion errors in the action handler logic, surfacing failed
__pycache__ paths or returning an error response instead of unconditionally
reporting success. Use the clear_pycache block and the
PROJECT_ROOT.rglob('__pycache__') cleanup loop as the place to add explicit
failure handling and logging.
- Around line 1677-1683: The API response is exposing the raw origin remote URL,
which can leak embedded credentials to the browser. In the endpoint that builds
the jsonify payload using subprocess.run('git remote get-url origin'), redact
the userinfo from remote.stdout before returning it, or remove the remote_url
field entirely. Update the response assembly so the Tools tab never receives a
sensitive repository token.
- Around line 1600-1602: Use the blueprint-scoped plugin manager consistently in
the install_plugin_requirements action. The current branch in api_v3.py falls
back to a bare plugin_manager reference, which can break the injected contract
used elsewhere in this module; update the logic in the
install_plugin_requirements handling to use the same api_v3.plugin_manager
access pattern as the surrounding code before resolving plugins_dir and scanning
requirements.txt.

In `@web_interface/templates/v3/partials/tools.html`:
- Around line 266-299: Handle non-2xx responses from git-info before rendering
the default clean UI. In the fetch chain inside the git info panel code, check
the response status in the callback that reads from /api/v3/system/git-info (or
inspect the parsed payload for an error state) and short-circuit to the existing
error display instead of building the branch/dirty badge markup. Make sure the
rendering logic around d.dirty, d.status, and panel.innerHTML does not treat
missing fields as a successful “clean” result.

---

Nitpick comments:
In `@scripts/install_dependencies_apt.py`:
- Around line 20-37: The new installer helper functions, including _run and the
related helper routines in install_dependencies_apt.py, are missing type
annotations for parameters and return values, which makes the tuple-returning
behavior harder to understand. Add explicit type hints to each helper’s
signature, including _run’s command argument and boolean/string tuple return,
and apply the same treatment to the other newly added helper methods/functions
referenced in the diff so their inputs and outputs are clear and consistent with
the coding guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 364638af-da3d-4912-bfe1-bfa4e6f025e8

📥 Commits

Reviewing files that changed from the base of the PR and between fefc2d4 and d2c0c96.

📒 Files selected for processing (9)
  • first_time_install.sh
  • scripts/install/one-shot-install.sh
  • scripts/install_dependencies_apt.py
  • web_interface/blueprints/api_v3.py
  • web_interface/blueprints/pages_v3.py
  • web_interface/static/v3/js/widgets/custom-feeds.js
  • web_interface/templates/v3/base.html
  • web_interface/templates/v3/partials/display.html
  • web_interface/templates/v3/partials/tools.html

Comment thread first_time_install.sh
Comment thread first_time_install.sh Outdated
Comment thread scripts/install_dependencies_apt.py Outdated
Comment thread web_interface/blueprints/api_v3.py
Comment thread web_interface/blueprints/api_v3.py Outdated
Comment thread web_interface/blueprints/api_v3.py Outdated
Comment thread web_interface/templates/v3/partials/tools.html Outdated
Chuck and others added 3 commits June 29, 2026 13:29
install_dependencies_apt.py previously reported only which packages
failed, not why - the actual apt/pip error was discarded (apt) or
could scroll out of the on_error log tail (pip), leaving "Step 7:
Install web interface dependencies (line 915)" as the only visible
detail.

Capture command output for each install attempt and print a compact
DEPENDENCY INSTALLATION FAILURES summary with the last lines of error
output per package. Also run the installer with `python3 -u` for
real-time, correctly-ordered logging, and widen the on_error tail from
50 to 100 lines so the summary isn't cut off.
The JS dotToNested() helper converts indexed form fields like
feeds.custom_feeds.0.name into a dict {'0': {name:...}} rather than a
proper array. The form-data path already had fix_array_structures() to
convert those dicts back to arrays before schema validation, but the
JSON path (used by all web-UI saves) never ran that fix, so saving any
custom feed produced a schema validation error: "Expected type array,
got object".

Add _fix_json_arrays() immediately after schema loading on the JSON
path, mirroring the existing fix_array_structures() logic.

Also fix custom-feeds.js getValue() to omit the logo key entirely when
no logo is present instead of returning logo:null, which would fail
schema validation (logo expects type object).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- first_time_install.sh: add _clone_rpi_rgb() wrapper so retry() cleans
  up any partial rpi-rgb-led-matrix-master dir before each clone attempt
- first_time_install.sh: use apt-get -o DPkg::Lock::Timeout=180 so apt
  handles lock contention natively instead of relying solely on flock TOCTOU check
- install_dependencies_apt.py: pass DPkg::Lock::Timeout=180 to apt-get
  install to avoid failing when unattended-upgrades holds the lock
- install_dependencies_apt.py: add type annotations to all public helpers
- api_v3.py: fix install_plugin_requirements to read plugin_manager from
  api_v3 blueprint attribute instead of the always-None module variable
- tools.html: loadGitInfo() now checks r.ok before parsing JSON and
  surfaces d.status === 'error' with the server's message in the panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ChuckBuilds ChuckBuilds force-pushed the fix/news-ticker-json-array-validation branch from cb8a51b to 7e44ad3 Compare June 29, 2026 17:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
scripts/install_dependencies_apt.py (1)

9-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use structured logging for the new installer diagnostics.

These new status and failure paths still write directly to stdout, which makes Pi-side triage harder once the installer is run remotely or under a service manager. Please route the new messages through a module logger with a stable prefix/context instead of expanding the print() surface.

Suggested direction
+import logging
+
+LOGGER = logging.getLogger(__name__)
+
-print(f"Trying to install {apt_package} via apt...")
+LOGGER.info("[Dependency Install] Trying to install %s via apt", apt_package)

-print("DEPENDENCY INSTALLATION FAILURES - DETAILS")
+LOGGER.error("[Dependency Install] DEPENDENCY INSTALLATION FAILURES - DETAILS")

As per coding guidelines, **/*.py: "Implement comprehensive logging for remote debugging on Raspberry Pi" and "Use structured logging with context (e.g., [NHL Recent]) for logging messages".

Also applies to: 62-69, 89-99, 123-145, 197-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install_dependencies_apt.py` around lines 9 - 18, The new installer
diagnostics in install_dependencies_apt.py are still using direct stdout prints;
update the affected status and failure paths to use the module logger instead.
Route messages through a logger with a stable contextual prefix such as the
existing installer context, and keep the logic in the installer helper functions
so remote/service-manager logs remain structured and searchable. Pay special
attention to the diagnostic flow around the command execution and error-summary
paths, replacing print-based output with logger calls while preserving the same
information.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 4627-4656: The _fix_json_arrays helper in api_v3.py only
normalizes array-shaped dicts at the current object level, so nested arrays
inside array items can remain numeric-keyed dicts and break validation. Update
_fix_json_arrays to recurse into converted array elements when the schema’s
items type is object (using items.properties), and also handle existing list
values by walking each element so nested schemas are normalized too.
- Around line 1684-1685: The fallback plugin path in api_v3.plugin_manager
resolution is hardcoded to plugin-repos instead of using the configured plugin
directory. Update the plugin-dir lookup in api_v3.py to reuse the same
plugin-directory resolution pattern used elsewhere in the module, so the
fallback respects plugin_system.plugins_directory and deployments that use
plugins/. Keep the logic anchored around active_pm and plugins_dir, but avoid a
fixed PROJECT_ROOT / plugin-repos default.

In `@web_interface/templates/v3/partials/tools.html`:
- Around line 227-243: The fetch flow in the action handler currently calls
r.json() unconditionally, so HTML/plain-text error responses from
/api/v3/system/action can surface as SyntaxError instead of the intended
friendly message. Update the fetch path in the tools template to inspect the
response before parsing, only parse JSON when appropriate, and otherwise fall
back to a safe text-based error message while still passing a useful message
into showResult. Apply the same handling to the other matching fetch block
referenced in the comment so both paths behave consistently.

---

Nitpick comments:
In `@scripts/install_dependencies_apt.py`:
- Around line 9-18: The new installer diagnostics in install_dependencies_apt.py
are still using direct stdout prints; update the affected status and failure
paths to use the module logger instead. Route messages through a logger with a
stable contextual prefix such as the existing installer context, and keep the
logic in the installer helper functions so remote/service-manager logs remain
structured and searchable. Pay special attention to the diagnostic flow around
the command execution and error-summary paths, replacing print-based output with
logger calls while preserving the same information.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a7deb92-b654-49d6-9792-ddd995d16f7e

📥 Commits

Reviewing files that changed from the base of the PR and between d2c0c96 and 7e44ad3.

📒 Files selected for processing (5)
  • first_time_install.sh
  • scripts/install_dependencies_apt.py
  • web_interface/blueprints/api_v3.py
  • web_interface/static/v3/js/widgets/custom-feeds.js
  • web_interface/templates/v3/partials/tools.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • web_interface/static/v3/js/widgets/custom-feeds.js
  • first_time_install.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🧹 Nitpick comments (1)
scripts/install_dependencies_apt.py (1)

9-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use structured logging for the new installer diagnostics.

These new status and failure paths still write directly to stdout, which makes Pi-side triage harder once the installer is run remotely or under a service manager. Please route the new messages through a module logger with a stable prefix/context instead of expanding the print() surface.

Suggested direction
+import logging
+
+LOGGER = logging.getLogger(__name__)
+
-print(f"Trying to install {apt_package} via apt...")
+LOGGER.info("[Dependency Install] Trying to install %s via apt", apt_package)

-print("DEPENDENCY INSTALLATION FAILURES - DETAILS")
+LOGGER.error("[Dependency Install] DEPENDENCY INSTALLATION FAILURES - DETAILS")

As per coding guidelines, **/*.py: "Implement comprehensive logging for remote debugging on Raspberry Pi" and "Use structured logging with context (e.g., [NHL Recent]) for logging messages".

Also applies to: 62-69, 89-99, 123-145, 197-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install_dependencies_apt.py` around lines 9 - 18, The new installer
diagnostics in install_dependencies_apt.py are still using direct stdout prints;
update the affected status and failure paths to use the module logger instead.
Route messages through a logger with a stable contextual prefix such as the
existing installer context, and keep the logic in the installer helper functions
so remote/service-manager logs remain structured and searchable. Pay special
attention to the diagnostic flow around the command execution and error-summary
paths, replacing print-based output with logger calls while preserving the same
information.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 4627-4656: The _fix_json_arrays helper in api_v3.py only
normalizes array-shaped dicts at the current object level, so nested arrays
inside array items can remain numeric-keyed dicts and break validation. Update
_fix_json_arrays to recurse into converted array elements when the schema’s
items type is object (using items.properties), and also handle existing list
values by walking each element so nested schemas are normalized too.
- Around line 1684-1685: The fallback plugin path in api_v3.plugin_manager
resolution is hardcoded to plugin-repos instead of using the configured plugin
directory. Update the plugin-dir lookup in api_v3.py to reuse the same
plugin-directory resolution pattern used elsewhere in the module, so the
fallback respects plugin_system.plugins_directory and deployments that use
plugins/. Keep the logic anchored around active_pm and plugins_dir, but avoid a
fixed PROJECT_ROOT / plugin-repos default.

In `@web_interface/templates/v3/partials/tools.html`:
- Around line 227-243: The fetch flow in the action handler currently calls
r.json() unconditionally, so HTML/plain-text error responses from
/api/v3/system/action can surface as SyntaxError instead of the intended
friendly message. Update the fetch path in the tools template to inspect the
response before parsing, only parse JSON when appropriate, and otherwise fall
back to a safe text-based error message while still passing a useful message
into showResult. Apply the same handling to the other matching fetch block
referenced in the comment so both paths behave consistently.

---

Nitpick comments:
In `@scripts/install_dependencies_apt.py`:
- Around line 9-18: The new installer diagnostics in install_dependencies_apt.py
are still using direct stdout prints; update the affected status and failure
paths to use the module logger instead. Route messages through a logger with a
stable contextual prefix such as the existing installer context, and keep the
logic in the installer helper functions so remote/service-manager logs remain
structured and searchable. Pay special attention to the diagnostic flow around
the command execution and error-summary paths, replacing print-based output with
logger calls while preserving the same information.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a7deb92-b654-49d6-9792-ddd995d16f7e

📥 Commits

Reviewing files that changed from the base of the PR and between d2c0c96 and 7e44ad3.

📒 Files selected for processing (5)
  • first_time_install.sh
  • scripts/install_dependencies_apt.py
  • web_interface/blueprints/api_v3.py
  • web_interface/static/v3/js/widgets/custom-feeds.js
  • web_interface/templates/v3/partials/tools.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • web_interface/static/v3/js/widgets/custom-feeds.js
  • first_time_install.sh
🛑 Comments failed to post (3)
web_interface/blueprints/api_v3.py (2)

1684-1685: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the configured plugin directory in the fallback path.

When api_v3.plugin_manager is unavailable, this hardcodes plugin-repos/ and skips installs for deployments that use plugins/ or a custom plugin_system.plugins_directory. Reuse the same plugin-dir resolution pattern used elsewhere in this module instead of a fixed fallback.

Suggested fix
         elif action == 'install_plugin_requirements':
             active_pm = getattr(api_v3, 'plugin_manager', None)
-            plugins_dir = Path(active_pm.plugins_dir) if active_pm else PROJECT_ROOT / 'plugin-repos'
+            if active_pm:
+                plugins_dir = Path(active_pm.plugins_dir)
+            else:
+                plugin_system_config = (
+                    api_v3.config_manager.load_config().get('plugin_system', {})
+                    if api_v3.config_manager else {}
+                )
+                plugins_dir_name = plugin_system_config.get('plugins_directory', 'plugin-repos')
+                plugins_dir = (
+                    Path(plugins_dir_name)
+                    if os.path.isabs(plugins_dir_name)
+                    else PROJECT_ROOT / plugins_dir_name
+                )
             results = []

As per coding guidelines, "Handle different deployment contexts with environment awareness in code".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            active_pm = getattr(api_v3, 'plugin_manager', None)
            if active_pm:
                plugins_dir = Path(active_pm.plugins_dir)
            else:
                plugin_system_config = (
                    api_v3.config_manager.load_config().get('plugin_system', {})
                    if api_v3.config_manager else {}
                )
                plugins_dir_name = plugin_system_config.get('plugins_directory', 'plugin-repos')
                plugins_dir = (
                    Path(plugins_dir_name)
                    if os.path.isabs(plugins_dir_name)
                    else PROJECT_ROOT / plugins_dir_name
                )
            results = []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/blueprints/api_v3.py` around lines 1684 - 1685, The fallback
plugin path in api_v3.plugin_manager resolution is hardcoded to plugin-repos
instead of using the configured plugin directory. Update the plugin-dir lookup
in api_v3.py to reuse the same plugin-directory resolution pattern used
elsewhere in the module, so the fallback respects
plugin_system.plugins_directory and deployments that use plugins/. Keep the
logic anchored around active_pm and plugins_dir, but avoid a fixed PROJECT_ROOT
/ plugin-repos default.

Source: Coding guidelines


4627-4656: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Recurse into array items after converting JSON dicts to lists.

This fixes array-shaped dicts only at the current object level. If an array item is itself an object that contains schema-defined arrays, those nested arrays stay as numeric-keyed dicts and can still fail validation on JSON saves. Recurse through items.properties for converted/existing array elements too.

Suggested fix
         if 'application/json' in content_type and schema and 'properties' in schema:
             def _fix_json_arrays(cfg, props):
                 for k, ps in props.items():
                     if not isinstance(cfg, dict) or k not in cfg:
                         continue
                     pt = ps.get('type')
                     val = cfg[k]
                     if pt == 'array' and isinstance(val, dict):
                         keys = list(val.keys())
                         if keys and all(str(x).isdigit() for x in keys):
                             sorted_keys = sorted(keys, key=lambda x: int(str(x)))
                             items_schema = ps.get('items', {})
                             item_type = items_schema.get('type')
                             arr = [val[sk] for sk in sorted_keys]
                             if item_type in ('integer', 'number'):
                                 converted = []
                                 for v in arr:
                                     if isinstance(v, str):
                                         try:
                                             converted.append(int(v) if item_type == 'integer' else float(v))
                                         except (ValueError, TypeError):
                                             converted.append(v)
                                     else:
                                         converted.append(v)
                                 arr = converted
                             cfg[k] = arr
                         elif not keys:
                             cfg[k] = []
+                        val = cfg[k]
+                    if pt == 'array' and isinstance(val, list):
+                        items_schema = ps.get('items', {})
+                        if items_schema.get('type') == 'object' and 'properties' in items_schema:
+                            for item in val:
+                                if isinstance(item, dict):
+                                    _fix_json_arrays(item, items_schema['properties'])
                     elif pt == 'object' and 'properties' in ps and isinstance(val, dict):
                         _fix_json_arrays(val, ps['properties'])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            def _fix_json_arrays(cfg, props):
                for k, ps in props.items():
                    if not isinstance(cfg, dict) or k not in cfg:
                        continue
                    pt = ps.get('type')
                    val = cfg[k]
                    if pt == 'array' and isinstance(val, dict):
                        keys = list(val.keys())
                        if keys and all(str(x).isdigit() for x in keys):
                            sorted_keys = sorted(keys, key=lambda x: int(str(x)))
                            items_schema = ps.get('items', {})
                            item_type = items_schema.get('type')
                            arr = [val[sk] for sk in sorted_keys]
                            if item_type in ('integer', 'number'):
                                converted = []
                                for v in arr:
                                    if isinstance(v, str):
                                        try:
                                            converted.append(int(v) if item_type == 'integer' else float(v))
                                        except (ValueError, TypeError):
                                            converted.append(v)
                                    else:
                                        converted.append(v)
                                arr = converted
                            cfg[k] = arr
                        elif not keys:
                            cfg[k] = []
                        val = cfg[k]
                    if pt == 'array' and isinstance(val, list):
                        items_schema = ps.get('items', {})
                        if items_schema.get('type') == 'object' and 'properties' in items_schema:
                            for item in val:
                                if isinstance(item, dict):
                                    _fix_json_arrays(item, items_schema['properties'])
                    elif pt == 'object' and 'properties' in ps and isinstance(val, dict):
                        _fix_json_arrays(val, ps['properties'])
            _fix_json_arrays(plugin_config, schema['properties'])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/blueprints/api_v3.py` around lines 4627 - 4656, The
_fix_json_arrays helper in api_v3.py only normalizes array-shaped dicts at the
current object level, so nested arrays inside array items can remain
numeric-keyed dicts and break validation. Update _fix_json_arrays to recurse
into converted array elements when the schema’s items type is object (using
items.properties), and also handle existing list values by walking each element
so nested schemas are normalized too.
web_interface/templates/v3/partials/tools.html (1)

227-243: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle non-JSON error bodies before calling r.json().

Both fetch paths still assume error responses are JSON. If Flask, nginx, or a proxy returns an HTML/plain-text 500 page, the UI falls back to a raw SyntaxError instead of the intended friendly error message.

Suggested fix
-        fetch('/api/v3/system/action', {
+        fetch('/api/v3/system/action', {
             method: 'POST',
             headers: {'Content-Type': 'application/json'},
             body: JSON.stringify({action})
         })
-        .then(r => r.json())
+        .then(async r => {
+            const text = await r.text();
+            let data = {};
+            try { data = text ? JSON.parse(text) : {}; } catch {}
+            if (!r.ok) {
+                throw new Error(data.message || `Request failed (HTTP ${r.status})`);
+            }
+            return data;
+        })
@@
-        fetch('/api/v3/system/git-info')
-            .then(r => {
-                if (!r.ok) return r.json().then(d => Promise.reject(d.message || `HTTP ${r.status}`));
-                return r.json();
-            })
+        fetch('/api/v3/system/git-info')
+            .then(async r => {
+                const text = await r.text();
+                let d = {};
+                try { d = text ? JSON.parse(text) : {}; } catch {}
+                if (!r.ok) {
+                    throw new Error(d.message || `HTTP ${r.status}`);
+                }
+                return d;
+            })

Also applies to: 266-269

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/templates/v3/partials/tools.html` around lines 227 - 243, The
fetch flow in the action handler currently calls r.json() unconditionally, so
HTML/plain-text error responses from /api/v3/system/action can surface as
SyntaxError instead of the intended friendly message. Update the fetch path in
the tools template to inspect the response before parsing, only parse JSON when
appropriate, and otherwise fall back to a safe text-based error message while
still passing a useful message into showResult. Apply the same handling to the
other matching fetch block referenced in the comment so both paths behave
consistently.

- api_v3.py install_plugin_requirements: replace hardcoded plugin-repos
  fallback with config-driven resolution (plugin_system.plugins_directory),
  matching the pattern used elsewhere in the module
- api_v3.py _fix_json_arrays: recurse into converted and existing array
  elements when items.type is object, so nested numeric-keyed dicts inside
  array items are also normalized
- tools.html toolsAction: check r.ok before r.json() and recover
  gracefully from non-JSON error bodies (HTML 500 pages), consistent
  with the existing loadGitInfo guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ChuckBuilds ChuckBuilds merged commit 639e1c3 into main Jun 29, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant